use std::mem::MaybeUninit;


pub const HASH_SEGS:usize = 16;
pub const SHA1_BLOCK_SIZE:usize = 64;
pub const MH_SHA1_BLOCK_SIZE:usize = HASH_SEGS * SHA1_BLOCK_SIZE;
pub const SHA1_DIGEST_WORDS:usize = 5;
pub const AVX512_ALIGNED:usize = 64;

extern "C" {
    pub fn mh_sha1_init (ctx:*mut MhSha1Ctx) -> libc::c_int;

    pub fn mh_sha1_update_avx2 (ctx:*mut MhSha1Ctx, buffer:*const u8, len:u32) -> libc::c_int;
    pub fn mh_sha1_finalize_avx2 (ctx:*mut MhSha1Ctx, mh_sha1_digest:*mut u8) -> libc::c_int;

    pub fn mh_sha1_update_avx512 (ctx:*mut MhSha1Ctx,buffer:*const u8, len:u32) -> libc::c_int;
    pub fn mh_sha1_finalize_avx512 (ctx:*mut MhSha1Ctx, mh_sha1_digest:*mut u8) -> libc::c_int;
}

#[repr(C)]
pub struct MhSha1Ctx {
	mh_sha1_digest:[u32;SHA1_DIGEST_WORDS],// the digest of multi-hash SHA1

	total_length:u64,
	// Parameters for update feature, describe the lengths of input buffers in bytes
	partial_block_buffer:[u8;MH_SHA1_BLOCK_SIZE * 2],
	//  Padding the tail of input data for SHA1
	mh_sha1_interim_digests:[u8;std::mem::size_of::<u32>() * SHA1_DIGEST_WORDS * HASH_SEGS],
	//  Storing the SHA1 interim digests of  all 16 segments. Each time, it will be copied to stack for 64-byte alignment purpose.
	frame_buffer:[u8;MH_SHA1_BLOCK_SIZE + AVX512_ALIGNED],
	//  Re-structure sha1 block data from different segments to fit big endian. Use AVX512_ALIGNED for 64-byte alignment purpose.
}

impl MhSha1Ctx {
    pub fn new() -> Self {
        unsafe {
            let mut result = MaybeUninit::uninit();
            mh_sha1_init(result.as_mut_ptr());
            result.assume_init()
        }
    }

    pub fn update_avx2(&mut self,data:&[u8]) {
        unsafe {
            mh_sha1_update_avx2(self as *mut MhSha1Ctx, &data[0] as *const u8, data.len().try_into().unwrap());
        }
    }

    pub fn finalize_avx2(&mut self,data:&mut [u8;20]) {
        unsafe {
            mh_sha1_finalize_avx2(self as *mut MhSha1Ctx, &mut data[0] as *mut u8);
        }
    }

    pub fn update_avx512(&mut self,data:&[u8]) {
        unsafe {
            mh_sha1_update_avx2(self as *mut MhSha1Ctx, &data[0] as *const u8, data.len().try_into().unwrap());
        }
    }

    pub fn finalize_avx512(&mut self,data:&mut [u8;20]) {
        unsafe {
            mh_sha1_finalize_avx512(self as *mut MhSha1Ctx, &mut data[0] as *mut u8);
        }
    }
}

#[derive(PartialEq, Eq, Hash,Clone, Copy, Debug, Default)]
pub struct Sha1Avx2 {
    inner:[u8;20],
}

impl util::FingerPrintHasher for Sha1Avx2 {
    fn as_mut(&mut self) -> &mut[u8] {
        &mut self.inner
    }

    fn as_ref(&self) -> &[u8] {
        &self.inner
    }

    fn fp(data:&[u8]) -> Self {
        unsafe {
            let mut result:MaybeUninit<Sha1Avx2> = MaybeUninit::uninit();
            let mut ctx = MhSha1Ctx::new();
            ctx.update_avx2(data);
            ctx.finalize_avx2(&mut (*result.as_mut_ptr()).inner); 
            result.assume_init()
        }
    }
}

impl From<[u8;20]> for Sha1Avx2 {
    fn from(value: [u8;20]) -> Self {
        Self { inner: value }
    }
}


impl AsRef<[u8]> for Sha1Avx2 {
    fn as_ref(&self) -> &[u8] {
        &self.inner
    }
}

#[derive(PartialEq, Eq, Hash,Clone, Copy, Debug, Default)]
pub struct Sha1Avx512 {
    inner:[u8;20],
}

impl util::FingerPrintHasher for Sha1Avx512 {
    fn as_mut(&mut self) -> &mut[u8] {
        &mut self.inner
    }

    fn as_ref(&self) -> &[u8] {
        &self.inner
    }

    fn fp(data:&[u8]) -> Self {
        unsafe {
            let mut result:MaybeUninit<Sha1Avx512> = MaybeUninit::uninit();
            let mut ctx = MhSha1Ctx::new();
            ctx.update_avx512(data);
            ctx.finalize_avx512(&mut (*result.as_mut_ptr()).inner); 
            result.assume_init()
        }
    }
}

impl From<[u8;20]> for Sha1Avx512 {
    fn from(value: [u8;20]) -> Self {
        Self { inner: value }
    }
}

impl AsRef<[u8]> for Sha1Avx512 {
    fn as_ref(&self) -> &[u8] {
        &self.inner
    }
}